package me.zhengjie.modules.fund.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.exception.EntityExistException;
import me.zhengjie.modules.fund.domain.StockNetValue;
import me.zhengjie.modules.fund.domain.UserStock;
import me.zhengjie.modules.fund.repository.UserStockRepository;
import me.zhengjie.modules.fund.reptile.StockCrawler;
import me.zhengjie.modules.fund.service.StockNetValueService;
import me.zhengjie.modules.fund.service.UserStockService;
import me.zhengjie.modules.fund.service.dto.UserStockDto;
import me.zhengjie.modules.fund.service.dto.UserStockQueryCriteria;
import me.zhengjie.modules.fund.service.mapstruct.UserStockMapper;
import me.zhengjie.utils.ElAdminConstant;
import me.zhengjie.utils.QueryHelp;
import me.zhengjie.utils.ValidationUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserStockServiceImpl implements UserStockService {

    private final UserStockRepository userStockRepository;

    private final UserStockMapper userStockMapper;

    private final StockCrawler stockCrawler;

    private final StockNetValueService stockNetValueService;

    @Override
    public UserStockDto findById(Long id) {
        UserStock userStock = userStockRepository.findById(id).orElseGet(UserStock::new);
        ValidationUtil.isNull(userStock.getId(), "UserStock", "id", id);
        return userStockMapper.toDto(userStock);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(UserStock resource) throws IOException {
        UserStock userStock = userStockRepository.findByUserIdAndStockCode(resource.getUserId(), resource.getStockCode());
        if (Objects.nonNull(userStock)) {
            throw new EntityExistException(UserStock.class, "code", resource.getStockCode());
        }

        resource.setPrincipalAmount(resource.getPrincipalNetValue().multiply(resource.getCount())); //成本金额

        List<StockNetValue> lastedNetValues = stockCrawler.getLastedNetValues(resource.getStockCode());
        if (CollectionUtils.isEmpty(lastedNetValues)) {
            throw new BadRequestException("查找不到 code 为" + resource.getStockCode() + "的股票");
        }
        stockNetValueService.save(lastedNetValues);
        StockNetValue stockNetValue = lastedNetValues.get(0);

        resource.setStockName(stockNetValue.getName());
        resource.setNewestNetValue(stockNetValue.getNetValue());
        resource.setNewestAmount(resource.getCount().multiply(resource.getNewestNetValue()));
        resource.setNewestUpdatedDate(stockNetValue.getDate());

        userStockRepository.save(resource);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(UserStock resource) throws IOException {
        UserStock userStock = userStockRepository.findById(resource.getId()).orElseGet(UserStock::new);
        ValidationUtil.isNull(userStock.getId(), "UserStock", "id", resource.getId());
        resource.setId(userStock.getId());

        resource.setPrincipalAmount(resource.getPrincipalNetValue().multiply(resource.getCount())); //成本金额

        List<StockNetValue> lastedNetValues = stockCrawler.getLastedNetValues(resource.getStockCode());
        if (CollectionUtils.isEmpty(lastedNetValues)) {
            throw new BadRequestException("查找不到 code 为" + resource.getStockCode() + "的股票");
        }
        stockNetValueService.save(lastedNetValues);
        StockNetValue stockNetValue = lastedNetValues.get(0);

        resource.setStockName(stockNetValue.getName());
        resource.setNewestNetValue(stockNetValue.getNetValue());
        resource.setNewestAmount(resource.getCount().multiply(resource.getNewestNetValue()));
        resource.setNewestUpdatedDate(stockNetValue.getDate());

        userStockRepository.save(resource);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateNetValue(StockNetValue stockNetValue) {
        List<UserStock> lstUserStocks = userStockRepository.findByStockCode(stockNetValue.getCode());
        if (CollectionUtils.isEmpty(lstUserStocks)) {
            log.error("没有找到指定编码:{}的用户股票", stockNetValue.getCode());
            return;
        }

        for (UserStock userStock : lstUserStocks) {
            userStock.setNewestNetValue(stockNetValue.getNetValue());
            userStock.setNewestUpdatedDate(stockNetValue.getDate());
            userStock.setNewestAmount(userStock.getCount().multiply(userStock.getNewestNetValue()));
        }

        userStockRepository.saveAll(lstUserStocks);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Set<Long> ids) {
        userStockRepository.deleteAllByIdIn(ids);
    }

    @Override
    public Page<UserStockDto> queryAll(UserStockQueryCriteria criteria, Pageable pageable) {
        Page<UserStock> userStocks = userStockRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
        List<UserStockDto> userStockDtos = userStocks.map(userStockMapper::toDto).getContent();

        BigDecimal totalPrincipleAmount = BigDecimal.ZERO; // 总本金
        BigDecimal totalNewestAmount = BigDecimal.ZERO; // 最新金额

        for (UserStockDto userStockDto : userStockDtos) {

            userStockDto.setProfitAmount(userStockDto.getNewestAmount().subtract(userStockDto.getPrincipalAmount()));
            if (userStockDto.getPrincipalAmount().compareTo(BigDecimal.ZERO) < 0) {
                userStockDto.setProfitRate(1d);
            } else {
                userStockDto.setProfitRate(userStockDto.getProfitAmount().divide(userStockDto.getPrincipalAmount(), 4, BigDecimal.ROUND_HALF_UP).doubleValue());
            }

            totalPrincipleAmount = totalPrincipleAmount.add(userStockDto.getPrincipalAmount());
            totalNewestAmount = totalNewestAmount.add(userStockDto.getNewestAmount());
        }

        userStockDtos = sorted(criteria, userStockDtos);

        List<UserStockDto> result = new ArrayList<>();

        if (!CollectionUtils.isEmpty(userStockDtos)) {
            UserStockDto total = new UserStockDto();
            total.setStockName("合计");
            total.setPrincipalAmount(totalPrincipleAmount);
            total.setNewestAmount(totalNewestAmount);
            total.setProfitAmount(totalNewestAmount.subtract(totalPrincipleAmount));
            total.setProfitRate(total.getProfitAmount().divide(total.getPrincipalAmount(), 4, BigDecimal.ROUND_HALF_UP).doubleValue());
            result.add(total);
        }

        result.addAll(userStockDtos);
        return new PageImpl<>(result, pageable, userStocks.getTotalElements());
    }

    /**
     * 根据条件进行排序
     *
     * @param criteria
     * @param userStockDtos
     * @return
     */
    private List<UserStockDto> sorted(UserStockQueryCriteria criteria, List<UserStockDto> userStockDtos) {
        if (Objects.nonNull(criteria.getSortBy()) && Objects.nonNull(criteria.getSortDirection())) {
            switch (criteria.getSortBy()) {
                case ElAdminConstant.UserStockSortBy.PROFIT_RATE:
                    if ("asc".equals(criteria.getSortDirection())) {
                        userStockDtos = userStockDtos.stream().sorted(Comparator.comparing(UserStockDto::getProfitRate)).collect(Collectors.toList());
                    } else {
                        userStockDtos = userStockDtos.stream().sorted(Comparator.comparing(UserStockDto::getProfitRate).reversed()).collect(Collectors.toList());
                    }
                    break;
                case ElAdminConstant.UserStockSortBy.PROFIT_AMOUNT:
                    if ("asc".equals(criteria.getSortDirection())) {
                        userStockDtos = userStockDtos.stream().sorted(Comparator.comparing(UserStockDto::getProfitAmount)).collect(Collectors.toList());
                    } else {
                        userStockDtos = userStockDtos.stream().sorted(Comparator.comparing(UserStockDto::getProfitAmount).reversed()).collect(Collectors.toList());
                    }
                    break;
            }
        }
        return userStockDtos;
    }
}
